package com.eastmoney.gamecode.util;

import com.eastmoney.gamecode.OutOfMapException;
import com.eastmoney.gamecode.constant.GameConstants;
import com.eastmoney.gamecode.enums.MapElementEnums;
import com.eastmoney.gamecode.enums.MoveTypeEnums;
import com.eastmoney.gamecode.enums.PropTypeEnums;
import com.eastmoney.gamecode.vo.*;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 工具类
 *
 * @author Liang Desheng
 * @version 1.0
 * @since 2023/9/29 16:42
 */
@Slf4j
public class GameUtils {

    private static final int RANG = 4;

    public static Optional<PropVo> getPropByXY(GameRequest gameRequest, Loc loc) {
        int x = loc.x * GameConstants.DOWNSAMPLING;
        int y = loc.y * GameConstants.DOWNSAMPLING;
        return gameRequest.getPropVoList()
                .stream()
                .filter(it -> it.getX() == x && it.getY() == y)
                .findFirst();
    }


    /**
     * 获取可达道具列表
     *
     * @return 可达道具列表
     */
    public static Loc calcNextLoc(GameRequest gameRequest, Set<Loc> hasDangerDirection) {
        GameMap map = gameRequest.getMap();
        TankSateVo myTank = gameRequest.getMyTank();

        List<Rect> result = new ArrayList<>();

        // 初始化遍历标识与距离
        boolean[][] walked = new boolean[map.ySize()][map.xSize()];
        int[][] distance = new int[map.ySize()][map.xSize()];
        gameRequest.setDistance(distance);

        for (int y = 0; y < map.ySize(); y++) {
            for (int x = 0; x < map.xSize(); x++) {
                if (!map.loc(x, y).canMove() && isEdge(map, x, y)) {
                    for (int i = -RANG; i <= RANG; i++) {
                        for (int j = -RANG; j <= RANG; j++) {
                            setWalked(distance, y + j, x + i, walked);
                        }
                    }
                } else {
                    distance[y][x] = Integer.MAX_VALUE / 2;
                }
            }
        }

        for (TankSateVo tankSateVo : gameRequest.getTankSatesVoList()) {
            if (tankSateVo == gameRequest.getMyTank()) {
                continue;
            }
            for (int i = -RANG + 1; i <= RANG - 1; i++) {
                Loc loc = tankSateVo.toLoc();
//                setWalked(distance, loc.y, loc.x + i, walked);
                setNotWalked(distance, loc.y, loc.x + i, walked);
            }
        }


        Loc curLoc = myTank.toLoc();
        Deque<Loc> stack = new ArrayDeque<>();
        distance[curLoc.y][curLoc.x] = 0;

        setDangerousWalked(gameRequest, walked, distance, curLoc);

        walked[curLoc.y][curLoc.x] = true;
        stack.addLast(curLoc);

        // log.info("填充距离");
        calcDisByBfs(gameRequest, map, result, walked, distance, stack);

        // 1. 寻找最近的道具 或者 敌方坦克
        if (!result.isEmpty()) {
            for (Rect propVo : result) {

                if (propVo instanceof PropVo && propVo.isInSafeArea(gameRequest.getSafeAreaStateVo())) {
                    if (myTank.getHp() < 50 && ((PropVo) propVo).getPropTypeEnums() == PropTypeEnums.BLOOD) {
                        // log.info("准备找道具");
                        Optional<Loc> nextLocOp = walkUtilDisEqN(propVo.toLoc(), curLoc, distance, GameConstants.DIS);
                        if (nextLocOp.isPresent()) {
                            Loc nextLoc = nextLocOp.get();
                            // log.info("结束找道具, cur={}, next={}", curLoc, nextLoc);
                            Loc direction = Loc.of(nextLoc.x - curLoc.x, nextLoc.y - curLoc.y);
                            if (!hasDangerDirection.contains(direction)) {
                                return nextLoc;
                            }
                        }
                    }
                }
            }

            for (Rect propVo : result) {

                if (propVo instanceof PropVo && propVo.isInSafeArea(gameRequest.getSafeAreaStateVo())) {
                    if (myTank.getHp() == 100 && ((PropVo) propVo).getPropTypeEnums() == PropTypeEnums.BLOOD) {
                        continue;
                    }
                    // log.info("准备找道具");
                    Optional<Loc> nextLocOp = walkUtilDisEqN(propVo.toLoc(), curLoc, distance, GameConstants.DIS);
                    if (nextLocOp.isPresent()) {
                        Loc nextLoc = nextLocOp.get();
                        // log.info("结束找道具, cur={}, next={}", curLoc, nextLoc);
                        Loc direction = Loc.of(nextLoc.x - curLoc.x, nextLoc.y - curLoc.y);
                        if (!hasDangerDirection.contains(direction)) {
                            return nextLoc;
                        }
                    }
                }
            }

            for (Rect propVo : result) {

                if (propVo instanceof PropVo && propVo.isInSafeArea(gameRequest.getSafeAreaStateVo())) {
                    if (((PropVo) propVo).getPropTypeEnums() == PropTypeEnums.BLOOD) {
                        // log.info("准备找道具");
                        Optional<Loc> nextLocOp = walkUtilDisEqN(propVo.toLoc(), curLoc, distance, GameConstants.DIS);
                        if (nextLocOp.isPresent()) {
                            Loc nextLoc = nextLocOp.get();
                            // log.info("结束找道具, cur={}, next={}", curLoc, nextLoc);
                            Loc direction = Loc.of(nextLoc.x - curLoc.x, nextLoc.y - curLoc.y);
                            if (!hasDangerDirection.contains(direction)) {
                                return nextLoc;
                            }
                        }
                    }
                }
            }

        }
        // 进入安全区
        Loc safeArea = gameRequest.getSafeAreaStateVo().toLoc();
        Optional<Loc> nextLocOp = walkUtilDisEqN(safeArea, curLoc, distance, GameConstants.DIS);

        if (nextLocOp.isPresent()) {
            Loc nextLoc = nextLocOp.get();
            Loc direction = Loc.of(nextLoc.x - curLoc.x, nextLoc.y - curLoc.y);
            if (!hasDangerDirection.contains(direction)) {
                return nextLoc;
            }
        }

        // 找最近的砖块
        if (hasBrick(map)) {
            Optional<Loc> nextLoc1Op = getBrickDirect(map, distance, curLoc);
            if (nextLoc1Op.isPresent()) {
                Loc nextLoc1 = nextLoc1Op.get();
                Loc direction = Loc.of(nextLoc1.x - curLoc.x, nextLoc1.y - curLoc.y);
                if (!hasDangerDirection.contains(direction)) {
                    return nextLoc1;
                }
            }
        }

        return null;
    }

    private static boolean isEdge(GameMap map, int x, int y) {
        Loc cur = Loc.of(x, y);
        for (Loc direction : Loc.DIRECTIONS) {
            try {
                Loc move = cur.move(direction);
                if (map.loc(move).canMove()) {
                    return true;
                }
            } catch (Exception e) {

            }
        }
        return false;
    }

    private static void setDangerousWalked(GameRequest gameRequest, boolean[][] walked, int[][] distance, Loc curLoc) {
        GameMap map = gameRequest.getMap();
        int xSize = map.xSize();
        int ySize = map.ySize();
        for (int x = 0; x < xSize; x++) {
            for (int i = 0; i < 4; i++) {
                setWalked(distance, i, x, walked);
                setWalked(distance, ySize - 4 + i, x, walked);
            }
        }

        for (int y = 0; y < ySize; y++) {
            for (int i = 0; i < 4; i++) {
                setWalked(distance, y, i, walked);
                setWalked(distance, y, xSize - 4 + i, walked);
            }
        }

        TankSateVo myTank = gameRequest.getMyTank();
        gameRequest.getBulletSateVoList()
                .parallelStream()
                .filter(bulletSateVo -> !bulletSateVo.getTankId().equals(myTank.getTankId()))
                .filter(bulletSateVo -> {
                    int x = myTank.getX();
                    int y = myTank.getY();
                    int bulletSateVoX = bulletSateVo.getX();
                    int bulletSateVoY = bulletSateVo.getY();
                    return Math.sqrt((bulletSateVoX - x) * (bulletSateVoX - x) + (bulletSateVoY - y) * (bulletSateVoY - y)) < 150;
                }).forEach(bulletSateVo -> {
                    switch (bulletSateVo.getMovingDirect()) {
                        case LEFT:
                            for (int y = -6; y <= 6; y++) {
                                for (int x = -10; x < 0; x++) {
                                    setWalked(distance, curLoc.x + x, curLoc.y + y, walked);
                                }
                            }
                            break;
                        case RIGHT:
                            for (int y = -6; y <= 6; y++) {
                                for (int x = 0; x < 10; x++) {
                                    setWalked(distance, curLoc.x + x, curLoc.y + y, walked);
                                }
                            }
                            break;
                        case TOP:
                            for (int x = -6; x <= 6; x++) {
                                for (int y = -10; y < 0; y++) {
                                    setWalked(distance, curLoc.x + x, curLoc.y + y, walked);
                                }
                            }
                            break;
                        case DOWN:
                            for (int x = -6; x <= 6; x++) {
                                for (int y = 0; y < 10; y++) {
                                    setWalked(distance, curLoc.x + x, curLoc.y + y, walked);
                                }
                            }
                            break;
                        default:
                            break;
                    }
                });

    }

    private static Optional<Loc> getBrickDirect(GameMap map, int[][] distance, Loc curLoc) {
        for (int i = 0; i < map.xSize(); i++) {
            for (int j = 0; j < map.ySize(); j++) {
                if (map.loc(i, j) == MapElementEnums.B) {
                    for (Loc d : Loc.DIRECTIONS) {
                        try {
                            Loc move = Loc.of(i, j).move(d);
                            if (distance[move.y][move.x] < Integer.MAX_VALUE / 2) {
                                // log.info("准备找砖块");
                                return walkUtilDisEqN(move, curLoc, distance, GameConstants.DIS);
                                // log.info("结束找砖块, cur={}, next={}", curLoc, nextLoc);
                            }
                        } catch (Exception e) {

                        }
                    }
                }
            }
        }
        return Optional.empty();
    }

    private static void setNotWalked(int[][] distance, int y, int x, boolean[][] walked) {
        try {
            distance[y][x] = Integer.MAX_VALUE / 2;
            walked[y][x] = false;
        } catch (Exception e) {
            // ignore
        }
    }

    public static void printDis(int[][] distance) {
        String toString = Arrays.stream(distance).map(Arrays::toString).collect(Collectors.joining("\n"));
        System.out.println(toString);
    }

    private static boolean hasBrick(GameMap map) {
        for (List<MapElementEnums> mapElementEnums : map) {
            for (MapElementEnums mapElementEnum : mapElementEnums) {
                if (mapElementEnum == MapElementEnums.B) {
                    return true;
                }
            }
        }
        return false;
    }

    private static void setHRangWalked(int[][] distance, int y, Loc curLoc, boolean[][] walked) {
        for (int i = -RANG; i <= RANG; i++) {
            setWalked(distance, y, curLoc.x + i, walked);
        }
    }

    private static void setVRangWalked(int[][] distance, Loc curLoc, int x, boolean[][] walked) {
        for (int i = -RANG; i <= RANG; i++) {
            setWalked(distance, curLoc.y + i, x, walked);
        }
    }

    private static void setWalked(int[][] distance, int y, int x, boolean[][] walked) {
        try {
            distance[y][x] = Integer.MAX_VALUE;
            walked[y][x] = true;
        } catch (Exception e) {
            // ignore
        }
    }

    private static boolean inRange(int x, int y, int[][] distance) {
        return y >= 0 && x >= 0 && y < distance.length && x < distance[0].length;
    }

    private static Optional<Loc> walkUtilDisEqN(Loc cur, Loc target, int[][] distance, int n) {
        if (distance[cur.y][cur.x] <= n) {
            return Optional.of(cur);
        }


        List<Loc> list = new ArrayList<>(Loc.DIRECTIONS);
        Collections.shuffle(list);
        for (Loc direction : list) {
            try {
                Loc next = cur.move(direction);
                if (distance[next.y][next.x] == distance[cur.y][cur.x] - 1) {
                    return walkUtilDisEqN(next, target, distance, n);
                }
            } catch (Exception ignore) {

            }
        }
//        throw new RuntimeException("????");
        return Optional.empty();
    }

    /**
     * 填充距离数组
     *
     * @param gameRequest
     * @param map
     * @param result
     * @param walked
     * @param distance
     * @param stack
     */
    private static void calcDisByBfs(GameRequest gameRequest,
                                     GameMap map,
                                     List<Rect> result,
                                     boolean[][] walked,
                                     int[][] distance,
                                     Deque<Loc> stack) {
        while (!stack.isEmpty()) {
            Loc cur = stack.pollFirst();
            for (Loc direction : Loc.DIRECTIONS) {
                try {
                    Loc next = cur.move(direction);
                    if (walked[next.y][next.x]) {
                        continue;
                    } else {
                        walked[next.y][next.x] = true;
                        distance[next.y][next.x] = distance[cur.y][cur.x] + 1;
                    }

                    MapElementEnums nextElement = map.loc(next);
                    log.debug("next: {}, element: {}", next, nextElement);
                    if (nextElement.canMove()) {
                        stack.addLast(next);
                        if (nextElement.isProp()) {
                            log.debug("add prop: {}", next);
                            Optional<PropVo> propVoOptional = getPropByXY(gameRequest, next);
                            propVoOptional.ifPresent(result::add);
                        }
                        Optional<TankSateVo> tank = isTank(next, gameRequest);
                        tank.ifPresent(tankSateVo -> {
                            // log.info("add Tank: {}", next);
                            result.add(tankSateVo);
                        });
//                        if (result.size() > 2) {
//                            break;
//                        }
                    } else {
                        log.debug("skip next: {}", next);
                    }
                } catch (OutOfMapException e) {
                    log.debug(String.format("%s: %s%n", cur, e.getMessage()));
                }
            }
        }
    }

    private static Optional<TankSateVo> isTank(Loc next, GameRequest gameRequest) {

        return gameRequest.getTankSatesVoList()
                .stream()
                .filter(it -> it != gameRequest.getMyTank())
                .filter(it -> it.toLoc().getX() == next.x && it.toLoc().getY() == next.y)
                .findFirst();
    }

    /**
     * 计算四个方向上会被子弹打击到的有哪些方向
     */
    public static Set<Loc> calBulletNearBy(List<BulletSateVo> bulletSateVoList, TankSateVo myTank) {
        Set<Loc> bulletNearBy = new HashSet<>();
        for (BulletSateVo bulletSateVo : bulletSateVoList) {
            if (!bulletSateVo.getTankId().equals(myTank.getTankId())) {
                Set<Loc> mayMeetBulletLoc = isBulletNearBy(bulletSateVo, myTank);
                bulletNearBy.addAll(mayMeetBulletLoc);
            }
        }
        //大于3 哪个方向都有危险等于没有危险
        if (bulletNearBy.size() > 3) {
            bulletNearBy.clear();
        }
        return bulletNearBy;
    }

    /**
     * 判断往那边走会遇上子弹
     */
    private static Set<Loc> isBulletNearBy(BulletSateVo bulletSateVo, TankSateVo myTank) {
        Set<Loc> res = new HashSet<>();
        int minX;
        int maxX;
        int minY;
        int maxY;
        int minMyX;
        int maxMyX;
        int minMyY;
        int maxMyY;
//        int bulletOffset = 15;
//        int tankOffset = 10;
        int bulletOffset = 25;
        int tankOffset = 10;
        if (MoveTypeEnums.DOWN.equals(bulletSateVo.getMovingDirect())) {
            //子弹向下
            minX = bulletSateVo.getX() - bulletSateVo.getWidth() / 2 - 1;
            maxX = bulletSateVo.getX() + bulletSateVo.getWidth() / 2 + 1;
            minY = bulletSateVo.getY() - bulletSateVo.getHeight() / 2 - 1;
            maxY = bulletSateVo.getY() + bulletSateVo.getHeight() / 2 + 1 + bulletOffset;

            minMyX = myTank.getX() - myTank.getWidth() / 2;
            maxMyX = myTank.getX() + myTank.getWidth() / 2;
            minMyY = myTank.getY() - myTank.getHeight() / 2;
            maxMyY = myTank.getY() + myTank.getHeight() / 2 + tankOffset;

            if (isIntersection(minX, maxX, minY, maxY, minMyX, maxMyX, minMyY, maxMyY)) {
                res.add(Loc.of(0, 1));
            }
        }
        if (MoveTypeEnums.TOP.equals(bulletSateVo.getMovingDirect())) {
            //子弹向上
            minX = bulletSateVo.getX() - bulletSateVo.getWidth() / 2 - 1;
            maxX = bulletSateVo.getX() + bulletSateVo.getWidth() / 2 + 1;
            minY = bulletSateVo.getY() - bulletSateVo.getHeight() / 2 - 1 - bulletOffset;
            maxY = bulletSateVo.getY() + bulletSateVo.getHeight() / 2 + 1;

            minMyX = myTank.getX() - myTank.getWidth() / 2;
            maxMyX = myTank.getX() + myTank.getWidth() / 2;
            minMyY = myTank.getY() - myTank.getHeight() / 2 - tankOffset;
            maxMyY = myTank.getY() + myTank.getHeight() / 2;

            if (isIntersection(minX, maxX, minY, maxY, minMyX, maxMyX, minMyY, maxMyY)) {
                res.add(Loc.of(0, -1));
            }
        }
        if (MoveTypeEnums.LEFT.equals(bulletSateVo.getMovingDirect())) {
            //子弹向左
            minX = bulletSateVo.getX() - bulletSateVo.getWidth() / 2 - 1 - bulletOffset;
            maxX = bulletSateVo.getX() + bulletSateVo.getWidth() / 2 + 1;
            minY = bulletSateVo.getY() - bulletSateVo.getHeight() / 2 - 1;
            maxY = bulletSateVo.getY() + bulletSateVo.getHeight() / 2 + 1;

            minMyX = myTank.getX() - myTank.getWidth() / 2 + tankOffset;
            maxMyX = myTank.getX() + myTank.getWidth() / 2;
            minMyY = myTank.getY() - myTank.getHeight() / 2;
            maxMyY = myTank.getY() + myTank.getHeight() / 2;

            if (isIntersection(minX, maxX, minY, maxY, minMyX, maxMyX, minMyY, maxMyY)) {
                res.add(Loc.of(-1, 0));
            }
        }
        if (MoveTypeEnums.RIGHT.equals(bulletSateVo.getMovingDirect())) {
            //子弹向右
            minX = bulletSateVo.getX() - bulletSateVo.getWidth() / 2 - 1;
            maxX = bulletSateVo.getX() + bulletSateVo.getWidth() / 2 + 1 + bulletOffset;
            minY = bulletSateVo.getY() - bulletSateVo.getHeight() / 2 - 1;
            maxY = bulletSateVo.getY() + bulletSateVo.getHeight() / 2 + 1;

            minMyX = myTank.getX() - myTank.getWidth() / 2;
            maxMyX = myTank.getX() + myTank.getWidth() / 2 + tankOffset;
            minMyY = myTank.getY() - myTank.getHeight() / 2;
            maxMyY = myTank.getY() + myTank.getHeight() / 2;

            if (isIntersection(minX, maxX, minY, maxY, minMyX, maxMyX, minMyY, maxMyY)) {
                res.add(Loc.of(1, 0));
            }
        }

        return res;
    }

    private static boolean isIntersection(int minX, int maxX, int minY, int maxY, int minMyX, int maxMyX, int minMyY, int maxMyY) {
        int x0 = Math.max(minX, minMyX);
        int y0 = Math.max(minY, minMyY);
        int x1 = Math.min(maxX, maxMyX);
        int y1 = Math.min(maxY, maxMyY);

        return (x0 < x1 && y0 < y1) && ((x1 - x0) * (y1 - y0) > 10);
    }

}
